Passed
Push — master ( b04473...f9e2c0 )
by Rafael S.
01:46
created

bit-parser.js ➔ readBytesAsBits   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
c 1
b 0
f 0
nc 2
dl 0
loc 10
rs 9.4285
nop 3
1
/**
2
 * bit-parser: Functions to read and write bytes.
3
 * Copyright (c) 2017 Rafael da Silva Rocha.
4
 * https://github.com/rochars/byte-data
5
 * Float32 based on int-bits: https://github.com/Jam3/int-bits
6
 */
7
8
let f32 = new Float32Array(1);
9
let i32 = new Int32Array(f32.buffer);
10
11
12
/**
13
 * Functions to read data from bytes.
14
 * @enum {Function}
15
 */
16
const BitReader = {
17
18
    /**
19
     * Read 1 8-bit int from from bytes.
20
     * @param {!Array<number>|Uint8Array} bytes An array of bytes.
21
     * @param {number} i The index to read.
22
     * @return {number}
23
     */
24
    "read8Bit": function (bytes, i) {
25
        return bytes[i];
26
    },
27
28
    /**
29
     * Read 1 16-bit int from from bytes.
30
     * @param {!Array<number>|Uint8Array} bytes An array of bytes.
31
     * @param {number} i The index to read.
32
     * @return {number}
33
     */
34
    "read16Bit": function (bytes, i) {
35
        return bytes[1 + i] << 8 | bytes[i];
36
    },
37
38
    /**
39
     * Read 1 16-bit float from from bytes.
40
     * Thanks https://stackoverflow.com/a/8796597
41
     * @param {!Array<number>|Uint8Array} bytes An array of bytes.
42
     * @param {number} i The index to read.
43
     * @return {number}
44
     */
45
    "read16BitFloat": function (bytes, i) {
46
        let binary = parseInt(getBinary([bytes[i], bytes[i+1]]), 2);
47
        let exponent = (binary & 0x7C00) >> 10;
48
        let fraction = binary & 0x03FF;
49
        let floatValue;
50
        if (exponent) {
51
            floatValue =  Math.pow(2, exponent - 15) * (1 + fraction / 0x400);
52
        } else {
53
            floatValue = 6.103515625e-5 * (fraction / 0x400);
54
        }
55
        return  floatValue * (binary >> 15 ? -1 : 1);
56
    },
57
58
    /**
59
     * Read 1 24-bit int from from bytes.
60
     * @param {!Array<number>|Uint8Array} bytes An array of bytes.
61
     * @param {number} i The index to read.
62
     * @return {number}
63
     */
64
    "read24Bit": function (bytes, i) {
65
        return bytes[2 + i] << 16 | BitReader["read16Bit"](bytes, i);
66
    },
67
68
    /**
69
     * Read 1 32-bit int from from bytes.
70
     * @param {!Array<number>|Uint8Array} bytes An array of bytes.
71
     * @param {number} i The index to read.
72
     * @return {number}
73
     */
74
    "read32Bit": function (bytes, i) {
75
        return (bytes[3 + i] << 24 |
76
            BitReader["read24Bit"](bytes, i)) >>> 0;
77
    },
78
79
    /**
80
     * Read 1 32-bit float from from bytes.
81
     * @param {!Array<number>|Uint8Array} bytes An array of bytes.
82
     * @param {number} i The index to read.
83
     * @return {number}
84
     */
85
    "read32BitFloat": function (bytes, i) {
86
        i32[0] = BitReader["read32Bit"](bytes, i);
87
        return f32[0];
88
    },
89
90
    /**
91
     * Read 1 40-bit int from from bytes.
92
     * @param {!Array<number>|Uint8Array} bytes An array of bytes.
93
     * @param {number} i The index to read.
94
     * @return {number}
95
     */
96
    "read40Bit": function (bytes, i) {
97
        return readBytesAsBits(bytes, i, 5);
98
    },
99
100
    /**
101
     * Read 1 48-bit int from bytes.
102
     * @param {!Array<number>|Uint8Array} bytes An array of bytes.
103
     * @param {number} i The index to read.
104
     * @return {number}
105
     */
106
    "read48Bit": function (bytes, i) {
107
        return readBytesAsBits(bytes, i, 6);
108
    },
109
110
    /**
111
     * Read 1 64-bit double from bytes.
112
     * Thanks https://gist.github.com/kg/2192799
113
     * @param {!Array<number>|Uint8Array} bytes An array of bytes.
114
     * @param {number} x The index to read.
115
     * @return {number}
116
     */
117
    "read64BitFloat": function (bytes, x) {
118
        let binary = getBinary(bytes.slice(x, x + 8));
119
        let significandBin = "1" + binary.substr(1 + 11, 52);
120
        let val = 1;
121
        let significand = 0;
122
        let i = 0;
123
        while (i < significandBin.length) {
124
            significand += val * parseInt(significandBin.charAt(i), 10);
125
            val = val / 2;
126
            i++;
127
        }
128
        let sign = (binary.charAt(0) == "1") ? -1 : 1;
129
        let doubleValue = sign * significand *
130
            Math.pow(2, parseInt(binary.substr(1, 11), 2) - 1023);
131
        return doubleValue == -0 ? 0 : doubleValue;
132
    },
133
134
    /**
135
     * Read 1 char from bytes.
136
     * @param {!Array<number>|Uint8Array} bytes An array of bytes.
137
     * @param {number} i The index to read.
138
     * @return {string}
139
     */
140
    "readChar": function (bytes, i, type) {
141
        let chrs = "";
142
        let j = 0;
143
        let len = type.bits / 8;
144
        while(j < len) {
145
            chrs += String.fromCharCode(bytes[i+j]);
146
            j++;
147
        }
148
        return chrs;
149
    }
150
};
151
152
/**
153
 * Functions to write data to bytes.
154
 * @enum {Function}
155
 */
156
let BitWriter = {
157
158
    "write64BitFloat": function(bytes, number, j) {
159
        let bits = toFloat64(number);
160
        j = BitWriter["write32Bit"](bytes, bits[1], j);
161
        return BitWriter["write32Bit"](bytes, bits[0], j);
162
    },
163
164
    // Thanks https://github.com/majimboo/c-struct
165
    "write48Bit": function (bytes, number, j) {
166
        j = BitWriter["write40Bit"](bytes, number, j);
167
        bytes[j++] = number / 0x10000000000 & 0xFF;
168
        return j;
169
    },
170
171
    // Thanks https://github.com/majimboo/c-struct
172
    "write40Bit": function (bytes, number, j) {
173
        j = BitWriter["write32Bit"](bytes, number, j);
174
        bytes[j++] = number / 0x100000000 & 0xFF;
175
        return j;
176
    },
177
178
    "write32BitFloat": function (bytes, number, j) {
179
        f32[0] = number;
180
        j = BitWriter["write32Bit"](bytes, i32[0], j);
181
        return j;
182
    },
183
184
    "write32Bit": function (bytes, number, j) {
185
        j = BitWriter["write24Bit"](bytes, number, j);
186
        bytes[j++] = number >>> 24 & 0xFF;
187
        return j;
188
    },
189
190
    "write24Bit": function (bytes, number, j) {
191
        j = BitWriter["write16Bit"](bytes, number, j);
192
        bytes[j++] = number >>> 16 & 0xFF;
193
        return j;
194
    },
195
196
    "write16Bit": function (bytes, number, j) {
197
        bytes[j++] = number & 0xFF;
198
        bytes[j++] = number >>> 8 & 0xFF;
199
        return j;
200
    },
201
202
    "write16BitFloat": function (bytes, number, j) {
203
        let bits = toHalf(number);
204
        bytes[j] = bits & 0xFF;
205
        bytes[j+1] = bits >>> 8 & 0xFF;
206
        return j+2;
207
    },
208
209
    "write8Bit": function (bytes, number, j) {
210
        bytes[j++] = number & 0xFF;
211
        return j;
212
    },
213
214
    "write4Bit": function (bytes, number, j) {
215
        bytes[j++] = number & 0xF;
216
        return j;
217
    },
218
219
    "write2Bit": function (bytes, number, j) {
220
        bytes[j++] = number < 0 ? number + 4 : number;
221
        return j;
222
    },
223
224
    "write1Bit": function (bytes, number, j) {
225
        bytes[j++] = number ? 1 : 0;
226
        return j;
227
    },
228
229
    "writeString": function (bytes, string, j) {
230
        bytes[j++] = string.charCodeAt(0);
231
        return j;
232
    }
233
};
234
235
/**
236
 * Get a binary string representation of a value described as bytes.
237
 * @param {Array<number>|number} bytes The bytes.
238
 * @return {string}
239
 */
240
function getBinary(bytes) {
241
    let binary = "";
242
    let i = 0;
243
    let bytesLength = bytes.length;
244
    while(i < bytesLength) {
245
        let bits = bytes[i].toString(2);
246
        binary = Array(9 - bits.length).join("0") + bits + binary;
247
        i++;
248
    }
249
    return binary;
250
}
251
252
/**
253
 * Unpack a 64 bit float into two words.
254
 * Thanks https://stackoverflow.com/a/16043259
255
 * @param {number} value A float64 number.
256
 * @return {!Array<number>}
257
 */
258
function toFloat64(value) {
259
    if (value == 0) {
260
        return [0, 0];
261
    }
262
    let hiWord = 0;
263
    let loWord = 0;
264
    if (value <= 0) {
265
        hiWord = 0x80000000;
266
        value = -value;
267
    }
268
    let exponent = Math.floor(
269
        Math.log(value) / Math.log(2));
270
    let significand = Math.floor(
271
        (value / Math.pow(2, exponent)) * Math.pow(2, 52));
272
    loWord = significand & 0xFFFFFFFF;
273
    significand /= Math.pow(2, 32);
274
    exponent += 1023;
275
    hiWord = hiWord | (exponent << 20);
276
    hiWord = hiWord | (significand & ~(-1 << 20));
277
    return [hiWord, loWord];
278
}
279
280
/**
281
 * to-half: int bits of half-precision floating point values
282
 * Based on:
283
 * https://mail.mozilla.org/pipermail/es-discuss/2017-April/047994.html
284
 * https://github.com/rochars/byte-data
285
 * @param {number} val The float16 value.
286
 * @return {number}
287
 */
288
function toHalf(val) {
289
    f32[0] = val;
290
    let x = i32[0];
291
    let bits = (x >> 16) & 0x8000;
292
    let m = (x >> 12) & 0x07ff;
293
    let e = (x >> 23) & 0xff;
294
    if (e < 103) {
295
        return bits;
296
    }
297
    bits |= ((e - 112) << 10) | (m >> 1);
298
    bits += m & 1;
299
    return bits;
300
}
301
302
/**
303
 * Read a group of bytes by turning it to bits.
304
 * Useful for 40 & 48-bit, but underperform.
305
 * @param {!Array<number>|Uint8Array} bytes An array of bytes.
306
 * @param {number} i The index to read.
307
 * @param {number} numBytes The number of bytes
308
 *      (1 for 8-bit, 2 for 16-bit, etc).
309
 * @return {number}
310
 */
311
function readBytesAsBits(bytes, i, numBytes) {
312
    let j = numBytes-1;
313
    let byte = "";
314
    while (j >= 0) {
315
        let bits = bytes[j + i].toString(2);
316
        byte += Array(9 - bits.length).join("0") + bits;
317
        j--;
318
    }
319
    return parseInt(byte, 2);
320
}
321
322
module.exports.BitWriter = BitWriter;
323
module.exports.BitReader = BitReader;
324